/*
* Copyright (c) 2007 by Kirill Kolodyazhniy.
* See the file "license.terms" for information on usage and redistribution.
*/


#include "scenemanager.h"
#include "rendertarget.h"
#include "graphic.h"
#include "light.h"
#include "entity.h"
#include "movableobjectvisitor.h"
#include "material.h"
#include "resourcemanager.h"
#include "functors.h"

#ifdef _DEBUG
#ifdef _MSC_VER
   #define new new( _CLIENT_BLOCK, __FILE__, __LINE__)
#endif
#endif // _DEBUG

namespace Engine
{
	using namespace std;
	/**********************************************************************************************************/
	SceneManager* SceneManagerFactory::CreateInstance( const std::string& name)
	{
		return new SceneManager(name);
	}
	/**********************************************************************************************************/
	SceneManager::SceneManager(const std::string& name)
		:m_name(name)
		,m_ambientLight(Color::White)
		,m_rootSceneNode(new SceneNode("RootSceneNode"))
		,m_shadowFill(new ShadowFill)
		,m_particlesUpdateTask(new ParticlesUpdateTask())
		,m_physicsUpdateTask(new PhysicsUpdateTask())
		,m_clearScreenTask(new ClearScreenTask())
	{
		static_cast<PhysicsUpdateTask*>(m_physicsUpdateTask.GetPointer())->physics = &m_physicSystems;
		static_cast<ParticlesUpdateTask*>(m_particlesUpdateTask.GetPointer())->ps = &m_particleList;
	}
	/**********************************************************************************************************/
	SceneManager::~SceneManager()
	{
	}
	/**********************************************************************************************************/
	struct UpdatePhysics
	{
		bool operator()(PhysicSystemPtr& ptr)
		{
			if(ptr.IsNull())
			{
				return true;
			}
			else
			{
				ptr->Update();
			}
			return false;
		}
	};
	void SceneManager::PhysicsUpdateTask::Run()
	{
		if(0 != physics)
		{
			SceneManager::PhysicSystemsVector::iterator i = std::remove_if(physics->begin(),physics->end(),UpdatePhysics());
			if(i != physics->end())
				physics->erase(i,physics->end());
		}
	}
	/**********************************************************************************************************/
	void SceneManager::ParticlesUpdateTask::Run()
	{
		if(0 != ps)
			for_each(ps->begin(), ps->end(), m_fn(&ParticleSystem::Simulate));
	}
	/**********************************************************************************************************/
	void SceneManager::ClearScreenTask::Run()
	{
		if(0 != renderTarget)
		{
			Graphic::GetSingleton().GetRenderSystem()->SetRenderTarget(renderTarget);
			Graphic::GetSingleton().GetRenderSystem()->ClearFrameBuffer(FBT_COLOUR | FBT_DEPTH | FBT_STENCIL,renderTarget->GetBackGroundColor());
		}
	}
	/**********************************************************************************************************/
	void SceneManager::RenderScene(const Camera* camera,RenderTarget* renderTarget)
	{
		static_cast<ClearScreenTask*>(m_clearScreenTask.GetPointer())->renderTarget = renderTarget;
		ThreadPool::GetSingleton().Run(m_clearScreenTask);

		m_particlesUpdateTask->Wait();
		m_physicsUpdateTask->Wait();

		UpdateSceneGraph();//calculate world positions for all nodes.

		ThreadPool::GetSingleton().Run(m_physicsUpdateTask);
		ThreadPool::GetSingleton().Run(m_particlesUpdateTask);

		FindVisibleObjects(camera);

		m_clearScreenTask->Wait();

		Graphic::GetSingleton().GetRenderSystem()->SetAmbientLight(m_ambientLight);

		Graphic::GetSingleton().GetRenderSystem()->BeginFaceCount();

		Graphic::GetSingleton().GetRenderSystem()->BeginFrame();

		Graphic::GetSingleton().GetRenderSystem()->SetPolygonMode(camera->GetPolygonMode());

		Graphic::GetSingleton().GetRenderSystem()->SetProjectionMatrix(camera->GetProjMatrix());

		Graphic::GetSingleton().GetRenderSystem()->SetViewMatrix(camera->GetViewMatrix());

		Graphic::GetSingleton().GetRenderSystem()->UseLights(m_lightList);

		// Render scene content
		RenderVisibleObjects(camera);

		RenderSignal();

		Graphic::GetSingleton().GetRenderSystem()->EndFrame();

		camera->NotifyRenderedFaces(Graphic::GetSingleton().GetRenderSystem()->GetFaceCount());
	}
	/**********************************************************************************************************/
	CameraPtr SceneManager::CreateCamera(const std::string& name)
	{
		// Check name not used
		if (m_cameras.find(name) != m_cameras.end())
			throw std::invalid_argument("A camera with the name " + name + " already exists");


		Camera *c = new Camera(name, this);
		m_cameras.insert(CameraMap::value_type(name,  RefPtr<Camera, WeakPtrExtension>(c)));

		return m_cameras[name];
	}
	/**********************************************************************************************************/
	SceneNodePtr SceneManager::GetRootSceneNode()
	{
		return m_rootSceneNode;
	}
	/**********************************************************************************************************/
	void SceneManager::UpdateSceneGraph()
	{
		m_rootSceneNode->Update();
	}
	/**********************************************************************************************************/
	void SceneManager::FindVisibleObjects(const Camera* camera)
	{
		m_renderQueue.Reset();
		camera->UpdateRenderQueue(&m_renderQueue);
		m_rootSceneNode->FindVisibleObjects(camera, &m_renderQueue, &m_shadowsRenderQueue, m_lightList);
	}
	/**********************************************************************************************************/
	void SceneManager::SetAmbientLight(const Color& color)
	{
		m_ambientLight = color;
	}
	/**********************************************************************************************************/
	LightPtr SceneManager::CreateLight(const std::string& name)
	{
		// delegate to factory implementation
		LightPtr light;
		WeakUtils::UnsafeCopy(light, CreateMovableObject(name, "Light"));
		m_lightList.push_back(light);
		return light;
	}
	/**********************************************************************************************************/
	void SceneManager::DeleteAllLights()
	{
		MovableObjectVisitorVec<Light> lv;
		DeleteMovableObject(&lv);
		m_lightList.clear();
	}
	/**********************************************************************************************************/
	EntityPtr SceneManager::CreateEntity(const std::string& name, const std::string& file)
	{
		// delegate to factory implementation
		NamedParameters params;
		params["filename"] = file;
		EntityPtr ptr;
		WeakUtils::UnsafeCopy(ptr, CreateMovableObject(name, "Entity", &params));
		return ptr;
	}
	/**********************************************************************************************************/
	void SceneManager::DeleteAllEntities()
	{
		MovableObjectVisitorVec<Entity> ev;
		DeleteMovableObject(&ev);
	}
	/**********************************************************************************************************/
	ParticleSystemPtr SceneManager::CreateParticleSystem(const std::string& name, const std::string& material, unsigned int count)
	{
		std::stringstream buf;
		buf << count;
		NamedParameters params;
		params["material"] = material;
		params["count"] = buf.str();
		ParticleSystemPtr ps;
		WeakUtils::UnsafeCopy(ps, CreateMovableObject(name, "ParticleSystem", &params));
		m_particleList.push_back(ps);
		return ps;
	}
	/**********************************************************************************************************/
	void SceneManager::DeleteAllParticleSystems()
	{
		m_particlesUpdateTask->Wait();
		MovableObjectVisitorVec<ParticleSystem> pv;
		DeleteMovableObject(&pv);
		m_particleList.clear();
	}
	/**********************************************************************************************************/
	void SceneManager::DeleteMovableObject(MovableObjectVisitor* visitor)
	{
		for_each(m_movObjects.begin(), m_movObjects.end(), on2nd(b2nd(m_fn(&MovableObject::Accept), visitor)));

		visitor->ToFirst();
		while(MovableObject* obj = visitor->GetNext())
			DeleteMovableObject(obj->GetName());
	}
	/**********************************************************************************************************/
	void SceneManager::DeleteMovableObject(const std::string& name)
	{
		MovableObjectMap::iterator i = m_movObjects.find(name);
		if(i != m_movObjects.end())
		{
			MovableObjectVisitorVec<Light> lv;
			MovableObjectVisitorVec<ParticleSystem> ps;
			i->second->Accept(&lv);
			i->second->Accept(&ps);
			if(0 != lv.GetNext())
			{
				LightPtr lp;
				WeakUtils::UnsafeCopy(lp, i->second);
				std::vector<LightPtr>::iterator l = std::find(m_lightList.begin(),m_lightList.end(), lp);
				m_lightList.erase(l);
			}
			if(0 != ps.GetNext())
			{
				ParticleSystemPtr psp;
				WeakUtils::UnsafeCopy(psp, i->second);
				std::vector<ParticleSystemPtr>::iterator p = std::find(m_particleList.begin(),m_particleList.end(), psp);
				m_particleList.erase(p);
			}
			m_movObjects.erase(i);
		}
	}
	/**********************************************************************************************************/
	RefPtr<MovableObject, WeakPtrExtension> SceneManager::CreateMovableObject(const std::string& name, const std::string& type, const NamedParameters* parameters)
	{
		MovableObjectFactoryPtr factory = Graphic::GetSingleton().GetMovableObjectFactory(type);

		if (m_movObjects.find(name) != m_movObjects.end())
			throw std::logic_error("An object of type '" + type + "' with name '" + name + "' already exists. SceneManager::CreateMovableObject");

		factory->SetParameters(parameters);
		MovableObject* newObj = factory->CreateInstance(name);
		if(newObj == 0)
			throw std::logic_error("SceneManager::CreateMovableObject can't create " + type+ " object with name " + name);
		m_movObjects[name] = RefPtr<MovableObject, WeakPtrExtension>(newObj);
		return m_movObjects[name];
	}
	/**********************************************************************************************************/
	void SceneManager::RenderVisibleObjects(const Camera* camera)
	{
		m_renderQueue.Sort(camera);

		// Do solids
		RenderObjects(m_renderQueue.GetSolids());

		RenderShadows(camera);

		// Do transparents
		RenderObjects(m_renderQueue.GetTransparents());
	}
	/**********************************************************************************************************/
	void SceneManager::RenderObjects(const RenderQueue::RenderableSortGroupVector& items)
	{
		RenderQueue::RenderableSortGroupVector::const_iterator groupi = items.begin(), groupe = items.end();
		for(;groupi != groupe; ++groupi)
		{
			if(groupi->Renderables->empty())
				continue;
			if(!groupi->MaterialName.empty())
			{
				if(!(*groupi->Renderables->begin())->GetMaterial().IsNull())
				{
					TechniquePtr tech = (*groupi->Renderables->begin())->GetMaterial()->GetBestTechnique();
					if(!tech.IsNull())
					{
						//groupi->material->SetDefaultParameters(time,frameTime,pmWorld,pmView,pmProjection,pmWorldView,pmViewProjection, pmWorldViewProjection,pmInvView);
						for(unsigned int pass = 0; pass < tech->GetPassCount() ; ++pass)
						{
							PassPtr ppass = tech->GetPass(pass);
							ppass->Begin();							
							for_each(groupi->Renderables->begin(), groupi->Renderables->end(), bind1st(mem_fun(&SceneManager::RenderSingleObject), this));
							ppass->End();
						}
					}
					else
						throw std::logic_error("SceneManager::RenderObjects there are no best technique for material" + groupi->MaterialName);
				}
				else
					throw std::logic_error("SceneManager::RenderObjects renderable without material");
			}
			else
				throw std::logic_error("SceneManager::RenderObjects renderable without material");;
		}
	}
	/**********************************************************************************************************/
	void SceneManager::RenderSingleObject(const RenderablePtr rend)
	{
		Graphic::GetSingleton().GetRenderSystem()->SetWorldMatrix(rend->GetWorldTransform());
		Graphic::GetSingleton().GetRenderSystem()->Render(rend->GetRenderData());

	}
	/**********************************************************************************************************/
	void SceneManager::RenderShadows(const Camera* camera)
	{
		m_shadowsRenderQueue.Sort(camera);
		//render shadow volumes
		RenderObjects(m_shadowsRenderQueue.GetSolids());
		RenderObjects(m_shadowsRenderQueue.GetTransparents());

		Graphic::GetSingleton().GetRenderSystem()->SetViewMatrix(Matrix4<REAL>::IDENTITY);
		Graphic::GetSingleton().GetRenderSystem()->SetProjectionMatrix(Matrix4<REAL>::IDENTITY);
		if(!m_shadowsRenderQueue.GetSolids().empty() || !m_shadowsRenderQueue.GetTransparents().empty())
		{
			//render shadows
			m_shadowFill->Update(camera);

			TechniquePtr tech = m_shadowFill->GetMaterial()->GetBestTechnique();
			if(!tech.IsNull())
			{
				for(unsigned int pass = 0; pass < tech->GetPassCount() ; ++pass)
				{
					PassPtr ppass = tech->GetPass(pass);
					ppass->Begin();
					RenderSingleObject(m_shadowFill);
					ppass->End();
				}
			}
		}
		Graphic::GetSingleton().GetRenderSystem()->SetStencilCheckEnabled(false);
		Graphic::GetSingleton().GetRenderSystem()->SetStencilBufferParams();
		Graphic::GetSingleton().GetRenderSystem()->SetViewMatrix(camera->GetViewMatrix());
		Graphic::GetSingleton().GetRenderSystem()->SetProjectionMatrix(camera->GetProjMatrix());
	}
	/**********************************************************************************************************/
	void SceneManager::AddPhysicSystem(PhysicSystemPtr physic)
	{
		while(m_physicsUpdateTask->IsRuning());
		PhysicSystemsVector::iterator i = std::find(m_physicSystems.begin(),m_physicSystems.end(), physic);
		if(i == m_physicSystems.end())
		{
			m_physicSystems.push_back(physic);
			physic->SetRootSceneNode(GetRootSceneNode());
		}
	}
	/**********************************************************************************************************/

	/**********************************************************************************************************/
	ShadowFill::ShadowFill()
		:m_transform(Matrix4<REAL>::IDENTITY)
	{
		WeakUtils::UnsafeCopy(m_material, ResourceManager::GetSingleton().Create("shadowfill.material", "Material"));
		m_material->Load();

		m_vertexDeclaration = Graphic::GetSingleton().GetRenderSystem()->CreateVertexDeclaration();
		m_vertexDeclaration->AddElement(0, 0, VET_FLOAT3, VES_POSITION);

		m_indexData = Graphic::GetSingleton().GetRenderSystem()->CreateIndexBuffer(2, INDEX_BUFFER_16);
		unsigned short* indexDataPtr = reinterpret_cast<unsigned short*>(m_indexData->Lock());
		indexDataPtr[0] = 0;
		indexDataPtr[1] = 1;
		indexDataPtr[2] = 2;

		indexDataPtr[3] = 0;
		indexDataPtr[4] = 2;
		indexDataPtr[5] = 3;

		m_indexData->UnLock();

		m_vertexData = Graphic::GetSingleton().GetRenderSystem()->CreateVertexBuffer(4, m_vertexDeclaration->GetVertexSize(0));

		struct Vertex
		{
			float x;
			float y;
			float z;
		};
		Vertex* vertexDataPtr = static_cast<Vertex*>(m_vertexData->Lock());

		vertexDataPtr[0].x = 1;
		vertexDataPtr[0].y = -1;
		vertexDataPtr[0].z = 0;

		vertexDataPtr[1].x = 1;
		vertexDataPtr[1].y = 1;
		vertexDataPtr[1].z = 0;

		vertexDataPtr[2].x = -1;
		vertexDataPtr[2].y = 1;
		vertexDataPtr[2].z = 0;

		vertexDataPtr[3].x = -1;
		vertexDataPtr[3].y = -1;
		vertexDataPtr[3].z = 0;

		m_vertexData->UnLock();

		m_renderData = Graphic::GetSingleton().GetRenderSystem()->CreateRenderData(2, 4,m_vertexDeclaration,m_indexData,m_vertexData);
	}
	/**********************************************************************************************************/
	ShadowFill::~ShadowFill()
	{
		if(!m_material.IsNull())
		{
			std::string name = m_material->GetName();
			m_material.Delete();
			ResourceManager::GetSingleton().TryRemove(name);
		}
	}
	/**********************************************************************************************************/
	void ShadowFill::Update(const Camera* camera)
	{
		//m_transform = camera->GetViewMatrix().Inverse() * camera->GetProjMatrix().Inverse();// * Matrix4<REAL>::GetScale(10000,10000,10000);
	}
	/**********************************************************************************************************/
	const MaterialPtr ShadowFill::GetMaterial(void) const
	{
		return m_material;
	}
	/**********************************************************************************************************/
	RenderDataPtr ShadowFill::GetRenderData() const
	{
		return m_renderData;
	}
	/**********************************************************************************************************/
	const Matrix4<REAL>& ShadowFill::GetWorldTransform() const
	{
		return m_transform;
	}
	/**********************************************************************************************************/
	const HardWareBufferPtr ShadowFill::GetVertexData() const
	{
		return m_vertexData;
	}
	/**********************************************************************************************************/
	const HardWareBufferPtr ShadowFill::GetIndexData() const
	{
		return m_indexData;
	}
	/**********************************************************************************************************/
	const VertexDeclarationPtr ShadowFill::GetVertexDeclaration() const
	{
		return m_vertexDeclaration;
	}
	/**********************************************************************************************************/
}
